//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#include <gtest/gtest.h>

#include "hydra/thread/ThreadLocal.h"

using namespace Hydra;

Atomic<int> s_numConstructors;
Atomic<int> s_numDestructors;

struct ConstructAndDestructStruct
{
	ConstructAndDestructStruct() { s_numConstructors.getAndAdd(1); }
	~ConstructAndDestructStruct() { s_numDestructors.getAndAdd(1); }
};

struct NoConstructStruct : public ConstructAndDestructStruct	{ };
struct NoDestructStruct : public ConstructAndDestructStruct	{ };
struct NoConstructOrDestructStruct : public ConstructAndDestructStruct	{ };
struct BigConstructAndDestructStruct : public ConstructAndDestructStruct
{
	char m_stuff[1024];
};

//tell some lies about the previous structs, so we can test them properly
namespace Hydra
{
	namespace TypeTraits
	{
		template<>
		struct has_trivial_constructor<NoConstructStruct>
		{
			static const bool value = true;
		};
		template<>
		struct has_trivial_destructor<NoDestructStruct>
		{
			static const bool value = true;
		};
		template<>
		struct has_trivial_constructor<NoConstructOrDestructStruct>
		{
			static const bool value = true;
		};
		template<>
		struct has_trivial_destructor<NoConstructOrDestructStruct>
		{
			static const bool value = true;
		};
	}
}

template<typename T>
struct TestHelper
{
	static void GetAddr(TestHelper* helper)		{ helper->m_addr = helper->m_tls.get(); }
	ThreadLocal<T> m_tls;
	T* m_addr;
};

class ThreadLocalTest : public ::testing::Test
{
protected:
	static void GetAddrInt(ThreadLocalTest* test)					{ test->m_addr = test->m_tlsInt.get(); }
	ThreadLocal<int> m_tlsInt;
	void* m_addr;
};

TEST_F(ThreadLocalTest, Stuff)
{
	//test that different threads get different addresses
	{
		Thread t(GetAddrInt, this);
		t.Start(); t.Join();
		void* addr = m_tlsInt.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
	}

	//test that constructors and destructors get called
	{
		s_numConstructors = 0;
		s_numDestructors = 0;
		TestHelper<ConstructAndDestructStruct> tls;
		Thread t(&TestHelper<ConstructAndDestructStruct>::GetAddr, &tls);
		t.Start(); t.Join();
		void* addr = tls.m_tls.get();
		void* addr2 = tls.m_tls.get();	//multiple calls to make sure constructor is called only once per thread
		void* addr3 = tls.m_tls.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
		EXPECT_TRUE(addr==addr2);
		EXPECT_TRUE(addr==addr3);
		EXPECT_TRUE(s_numConstructors==2);
	}
	EXPECT_TRUE(s_numDestructors==2);

	//test that constructors don't get called when structure is small and constructor is trivial
	// NOTE: actually in this case the constructor will be called even though it is trivial, this is because the destructor is non-trivial and
	//       we save no work by having a specialization for this case, as the non-trivial destructor needs the init-on-first-use code anyway.
	{
		s_numConstructors = 0;
		s_numDestructors = 0;
		TestHelper<NoConstructStruct> tls;
		Thread t(&TestHelper<NoConstructStruct>::GetAddr, &tls);
		t.Start(); t.Join();
		void* addr = tls.m_tls.get();
		void* addr2 = tls.m_tls.get();	//multiple calls to make sure constructor is called only once per thread
		void* addr3 = tls.m_tls.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
		EXPECT_TRUE(addr==addr2);
		EXPECT_TRUE(addr==addr3);
		EXPECT_TRUE(s_numConstructors==2);
	}
	EXPECT_TRUE(s_numDestructors==2);

	//test that destructors don't get called when structure is small and destructor is trivial
	{
		s_numConstructors = 0;
		s_numDestructors = 0;
		TestHelper<NoDestructStruct> tls;
		Thread t(&TestHelper<NoDestructStruct>::GetAddr, &tls);
		t.Start(); t.Join();
		void* addr = tls.m_tls.get();
		void* addr2 = tls.m_tls.get();	//multiple calls to make sure constructor is called only once per thread
		void* addr3 = tls.m_tls.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
		EXPECT_TRUE(addr==addr2);
		EXPECT_TRUE(addr==addr3);
		EXPECT_TRUE(s_numConstructors==2);
	}
	EXPECT_TRUE(s_numDestructors==0);

	//test that neither constructors or destructors get called when structure is small and constructor and destructor are trivial
	{
		s_numConstructors = 0;
		s_numDestructors = 0;
		TestHelper<NoConstructOrDestructStruct> tls;
		Thread t(&TestHelper<NoConstructOrDestructStruct>::GetAddr, &tls);
		t.Start(); t.Join();
		void* addr = tls.m_tls.get();
		void* addr2 = tls.m_tls.get();	//multiple calls to make sure constructor is called only once per thread
		void* addr3 = tls.m_tls.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
		EXPECT_TRUE(addr==addr2);
		EXPECT_TRUE(addr==addr3);
		EXPECT_TRUE(s_numConstructors==0);
	}
	EXPECT_TRUE(s_numDestructors==0);

	//test that both constructors and destructors get called when structure is big enough to use dynamic allocation
	{
		s_numConstructors = 0;
		s_numDestructors = 0;
		TestHelper<BigConstructAndDestructStruct> tls;
		Thread t(&TestHelper<BigConstructAndDestructStruct>::GetAddr, &tls);
		t.Start(); t.Join();
		void* addr = tls.m_tls.get();
		void* addr2 = tls.m_tls.get();	//multiple calls to make sure constructor is called only once per thread
		void* addr3 = tls.m_tls.get();
		EXPECT_TRUE(addr != NULL);
		EXPECT_TRUE(m_addr != NULL);
		EXPECT_TRUE(addr!=m_addr);
		EXPECT_TRUE(addr==addr2);
		EXPECT_TRUE(addr==addr3);
		EXPECT_TRUE(s_numConstructors==2);
	}
	EXPECT_TRUE(s_numDestructors==2);
}

